package bloggo

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"net/http"
	"path"
	"strings"
	"time"
)

type AnonymousUrl struct {
	RequestMethod string `json:"request_method"`
	Url           string `json:"url"`
}

type UserResource struct {
	RoleId        string `json:"roleId"`
	ResourceName  string `json:"resourceName"`
	RequestMethod string `json:"requestMethod"`
	Url           string `json:"url"`
	IsAnonymous   int    `json:"isAnonymous"`
}

// AnonymousUrls  可匿名访问的接口
var AnonymousUrls func(ctx *gin.Context) (urls []AnonymousUrl)

// UserResources 用户资源
var UserResources func(ctx *gin.Context, userId string) (resList []UserResource, err error)

// SessionUser 载荷，可以加一些自己需要的信息
type SessionUser struct {
	UserId    string `json:"userId"`
	LoginCode string `json:"loginCode"`
	NickName  string `json:"nickName"`
	Email     string `json:"email"`
	Avatar    string `json:"avatar"`
	jwt.StandardClaims
}

func GetSessionUser() *SessionUser {
	s := tget("SessionUser")
	if s == nil {
		return nil
	}
	return s.(*SessionUser)
}
func SetSessionUser(user *SessionUser) {
	tset("SessionUser", user)
}
func DelSessionUser() {
	tdel("SessionUser")
}

//Authentication 认证
var Authentication gin.HandlerFunc = JWTAuth

// JWTAuth 中间件，检查token
func JWTAuth(c *gin.Context) {
	url := c.Request.URL.String()
	method := c.Request.Method

	noPermission := NoPermission()
	noPermission.Request = method + " " + url
	if url == "" {
		c.JSONP(http.StatusOK, noPermission)
		c.Abort()
		return
	}
	apiUrl := strings.Replace(url, Conf.ProjectRootPath, "", 1)
	if strings.Contains(apiUrl, "?") {
		apiUrl = strings.Split(apiUrl, "?")[0]
	}
	//访问的是否是可匿名访问的接口
	anonymousUrls := AnonymousUrls(c)
	isAnonymousUrl := false
	for _, item := range anonymousUrls {
		flag1, _ := path.Match(item.Url, apiUrl)
		if flag1 && method == item.RequestMethod {
			isAnonymousUrl = true
			break
		}
	}
	if isAnonymousUrl {
		c.Next()
		return
	}
	token := c.Request.Header.Get("token")
	if token == "" {
		noToken := NoToken()
		noToken.Request = method + " " + url
		c.JSONP(http.StatusOK, noToken)
		c.Abort()
		return
	}
	Log("get token: ", token)

	j := NewJWT()
	// parseToken 解析token包含的信息
	claims, r := j.ParseToken(token)
	if r != nil {
		r.Request = method + " " + url
		c.JSONP(http.StatusOK, r)
		c.Abort()
		return
	}
	//权限接口拦截
	resList, err := UserResources(c, claims.UserId)
	if err != nil {
		c.JSONP(http.StatusOK, noPermission)
		c.Abort()
		return
	}
	noPermissionsFlag := true
	for _, item := range resList {
		flag1, _ := path.Match(item.Url, apiUrl)
		if flag1 && method == item.RequestMethod {
			noPermissionsFlag = false
			break
		}
	}
	if noPermissionsFlag {
		c.JSONP(http.StatusOK, noPermission)
		c.Abort()
		return
	}
	// 继续交由下一个路由处理,并将解析出的信息传递下去
	SetSessionUser(claims)
	// 验证通过，会继续访问下一个中间件
	c.Next()
}

// JWT 签名结构
type JWT struct {
	SigningKey []byte
}

// NewJWT 新建一个jwt实例
func NewJWT() *JWT {
	return &JWT{
		[]byte(GetSignKey()),
	}
}

// GetSignKey 获取signKey
func GetSignKey() string {
	return Conf.Jwt.SignKey
}

// GetExpiresAt 获取expiresAt
func GetExpiresAt() int {
	return Conf.Jwt.ExpiresAt
}

// CreateToken 生成一个token
func (j *JWT) CreateToken(claims *SessionUser) (string, error) {
	now := time.Now()
	claims.StandardClaims = jwt.StandardClaims{
		ExpiresAt: now.Add(time.Duration(GetExpiresAt()) * time.Second).Unix(),
		IssuedAt:  now.Unix(),
		Issuer:    "system",
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.SigningKey)
}

// ParseToken 解析Token
func (j *JWT) ParseToken(tokenString string) (*SessionUser, *R) {
	token, err := jwt.ParseWithClaims(tokenString, &SessionUser{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})
	if err != nil {
		if ve, ok := err.(*jwt.ValidationError); ok {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, TokenMalformed()
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				// Token is expired
				return nil, TokenExpired()
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, TokenNotValidYet()
			} else {
				return nil, TokenInvalid()
			}
		}
	}
	if claims, ok := token.Claims.(*SessionUser); ok && token.Valid {
		return claims, nil
	}
	return nil, TokenInvalid()
}

// RefreshToken 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
	jwt.TimeFunc = func() time.Time {
		return time.Unix(0, 0)
	}
	token, err := jwt.ParseWithClaims(tokenString, &SessionUser{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})
	if err != nil {
		return "", err
	}
	if claims, ok := token.Claims.(*SessionUser); ok && token.Valid {
		jwt.TimeFunc = time.Now
		claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
		return j.CreateToken(claims)
	}
	return "", errors.New(TokenInvalid().Message)
}
